﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.DirectoryServices;
    using System.Diagnostics;
    using System.Threading;
    #endregion

    /// <summary>
    /// When querying for a domain, the base DN must be the local partition of the Domain Naming Context
    /// </summary>
    [Serializable]
    public class DomainProperties : ADBaseObjectProperties, IGroupPolicyAttributes {

        #region Members
        public DateTime CreationTime { get; private set; }

        /// <summary>
        /// The Domain Component of the LDAP name
        /// </summary>
        public string DC { get; private set; }

        /// <summary>
        /// The Domain Functional Level
        /// </summary>
        public MSDSBehaviorVersionDefinition DomainFunctionalLevel { get { return this.MSDSBehaviorVersion; } }

        /// <summary>
        /// Used in computing the kick off time in SamIGetAccountRestrictions. Logoff time minus Force Log off equals kick off time.
        /// </summary>
        public long ForceLogoff { get; private set; }

        /// <summary>
        /// The PDC Emulator role owner
        /// The DN of the NTDS Settings object of the Server object in the Site -> Configuration container
        /// </summary>
        public string FSMORoleOwner { get; private set; }

        public InstanceTypeFlags InstanceType { get { return instanceType; } }
        private InstanceTypeFlags instanceType;

        public TimeSpan LockoutDuration { get; private set; }

        public TimeSpan LockoutObservationWindow { get; private set; }

        public int LockoutThreshold { get; private set; }

        public TimeSpan MaxPasswordAge { get; private set; }

        public TimeSpan MinPasswordAge { get; private set; }

        public int MinPasswordLength { get; private set; }

        /// <summary>
        /// The Domain Functional Level
        /// </summary>
        public MSDSBehaviorVersionDefinition MSDSBehaviorVersion { get { return msDSBehaviorVersion; } }
        private MSDSBehaviorVersionDefinition msDSBehaviorVersion;

        /// <summary>
        /// The number of computer accounts that a user is allowed to create in a domain.
        /// </summary>
        public int MSDSMachineAccountQuota { get; private set; }

        /// <summary>
        /// This attribute specifies the list of servers that are the replica set for the corresponding non-domain NC. 
        /// For more information, see [MS-ADTS] section 6.1.1.2.1.1.5.
        /// </summary>
        public int MSDSNcType { get; private set; }

        public static readonly string ObjectCategoryShortName = "domainDNS";

        /// <summary>
        /// The number of passwords that are retained by the system for checking password changes
        /// </summary>
        public int PasswordHistoryLength { get; private set; }

        #region IGroupPolicyAttributes properties
        public string GPLink { get; private set; }

        public IReadOnlyList<string> GPLinkGPODNs { get; private set; }

        public string GPOptions { get; private set; }
        #endregion
        #endregion

        #region Constructor
        public DomainProperties() {
            this.Initialize();
        }

        public DomainProperties(DirectoryEntry directoryEntry, bool destroyInputObject = true)
            : base(directoryEntry, destroyInputObject) {
            if (this.GetType().Name == typeof(DomainProperties).Name) {
                this.Attributes = null;
            }
        }

        public DomainProperties(SearchResult result, bool destroyInputObject = true)
            : base(result, destroyInputObject) {
            if (this.GetType().Name == typeof(DomainProperties).Name) {
                this.Attributes = null;
            }
        }

        public DomainProperties(List<string> ldifOutput)
            : base(ldifOutput) {
            if (this.GetType().Name == typeof(DomainProperties).Name) {
                this.Attributes = null;
            }
        }
        #endregion

        #region Methods
        protected override void Initialize() {
            base.Initialize();

            this.DC = string.Empty;

            // IGroupPolicyAttributes
            this.GPLink = string.Empty;
            this.GPOptions = string.Empty;
            this.GPLinkGPODNs = new List<string>();
            msDSBehaviorVersion = MSDSBehaviorVersionDefinition.WIN2008R2;
        }

        protected override void ProcessResults() {

            try {
                base.ProcessResults();

                if (this.Attributes.ContainsKey("creationTime")) {
                    this.CreationTime = DateTime.FromFileTime(Convert.ToInt64(this.Attributes["creationTime"][0].ToString()));
                }
                if (this.Attributes.ContainsKey("dc")) {
                    this.DC = this.Attributes["dc"][0].ToString();
                }
                if (this.Attributes.ContainsKey("forceLogoff")) {
                    this.ForceLogoff = Convert.ToInt64(this.Attributes["forceLogoff"][0]);
                }
                if (this.Attributes.ContainsKey("fSMORoleOwner")) {
                    this.FSMORoleOwner = this.Attributes["fSMORoleOwner"][0].ToString();
                }
                if (this.Attributes.ContainsKey("gpLink")) {
                    this.GPLink = this.Attributes["gpLink"][0].ToString();
                }
                if (this.Attributes.ContainsKey("gpOptions")) {
                    this.GPOptions = this.Attributes["gpOptions"][0].ToString();
                }

                this.GPLinkGPODNs = ADDataAccess.GetGPLinkGPODNs(this.GPLink);

                if (!string.IsNullOrWhiteSpace(this.GPLink) && this.GPLinkGPODNs.Count == 0) {
                    Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} DN: {3} Warning: gpLink may contain invalid data: {4}",
                        DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId,
                        CustomExtensions.CurrentMethodName(), this.DN, this.GPLink));
                }

                if (this.Attributes.ContainsKey("instanceType")) {
                    Enum.TryParse<InstanceTypeFlags>(this.Attributes["instanceType"][0].ToString(), ignoreCase: true, result: out instanceType);
                }
                if (this.Attributes.ContainsKey("lockoutDuration")) {
                    var ticks = Convert.ToInt64(this.Attributes["lockoutDuration"][0]);
                    if ((ticks < TimeSpan.MaxValue.Ticks) && (ticks > TimeSpan.MinValue.Ticks)) {
                        this.LockoutDuration = TimeSpan.FromTicks(ticks).Duration();
                    }
                }
                if (this.Attributes.ContainsKey("lockOutObservationWindow")) {
                    var ticks = Convert.ToInt64(this.Attributes["lockOutObservationWindow"][0]);
                    if ((ticks < TimeSpan.MaxValue.Ticks) && (ticks > TimeSpan.MinValue.Ticks)) {
                        this.LockoutObservationWindow = TimeSpan.FromTicks(ticks).Duration();
                    }
                }
                if (this.Attributes.ContainsKey("lockoutThreshold")) {
                    this.LockoutThreshold = Convert.ToInt32(this.Attributes["lockoutThreshold"][0]);
                }
                if (this.Attributes.ContainsKey("maxPwdAge")) {
                    var ticks = Convert.ToInt64(this.Attributes["maxPwdAge"][0]);
                    if ((ticks < TimeSpan.MaxValue.Ticks) && (ticks > TimeSpan.MinValue.Ticks)) {
                        this.MaxPasswordAge = TimeSpan.FromTicks(ticks).Duration();
                    }
                }
                if (this.Attributes.ContainsKey("minPwdAge")) {
                    var ticks = Convert.ToInt64(this.Attributes["minPwdAge"][0]);
                    if ((ticks < TimeSpan.MaxValue.Ticks) && (ticks > TimeSpan.MinValue.Ticks)) {
                        this.MinPasswordAge = TimeSpan.FromTicks(ticks).Duration();
                    }
                }
                if (this.Attributes.ContainsKey("minPwdLength")) {
                    this.MinPasswordLength = Convert.ToInt32(this.Attributes["minPwdLength"][0]);
                }
                if (this.Attributes.ContainsKey("msDS-Behavior-Version")) {
                    Enum.TryParse<MSDSBehaviorVersionDefinition>(
                        this.Attributes["msDS-Behavior-Version"][0].ToString(), ignoreCase: true, result: out msDSBehaviorVersion);
                }
                if (this.Attributes.ContainsKey("ms-DS-MachineAccountQuota")) {
                    this.MSDSMachineAccountQuota = Convert.ToInt32(this.Attributes["ms-DS-MachineAccountQuota"][0]);
                }
                if (this.Attributes.ContainsKey("msDS-NcType")) {
                    this.MSDSNcType = Convert.ToInt32(this.Attributes["msDS-NcType"][0]);
                }
                if (this.Attributes.ContainsKey("pwdHistoryLength")) {
                    this.PasswordHistoryLength = Convert.ToInt32(this.Attributes["pwdHistoryLength"][0]);
                }

                this.IsValid = !string.IsNullOrWhiteSpace(this.DN)
                    && this.ObjectClass.Any(x => string.Equals(x, ObjectCategoryShortName, StringComparison.OrdinalIgnoreCase));
            }
            catch (Exception e) {
                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Error: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), e.VerboseExceptionString()));
                throw;
            }
        }
        [DebuggerStepThroughAttribute]
        public override string ToString() {
            string details = this.DC;
            if (string.IsNullOrWhiteSpace(details)) details = "N/A";
            return details;
        }
        #endregion
    }
}
